#include <stdio.h>
#include <setjmp.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <stdlib.h>
#include "lib/threads.h"
#include <gmp.h>

#ifdef __x86_64__
// code for 64 bit Intel arch

//A translation required when using an address of a variable
address_t translate_address(address_t addr)
{
    address_t ret;
    asm volatile("xor    %%fs:0x30,%0\n"
        "rol    $0x11,%0\n"
                 : "=g" (ret)
                 : "0" (addr));
    return ret;
}

#else
// code for 32 bit Intel arch

//A translation required when using an address of a variable
address_t translate_address(address_t addr)
{
    address_t ret;
    asm volatile("xor    %%gs:0x18,%0\n"
        "rol    $0x9,%0\n"
                 : "=g" (ret)
                 : "0" (addr));
    return ret;
}

#endif

/* Running Thread */
void get_pi();
void factorial(unsigned long int n, mpz_t fact);

void factorial(unsigned long int n, mpz_t fact)
{
	unsigned long int  num = n;
	mpz_fac_ui (fact, num);
}

void get_pi(void)
{
	
    gchar *str;  
    
    // Getting current progress bar
	gpointer *pbar = g_ptr_array_index(config->thread_progressbars, currentThread->id);
	GtkWidget *sbar = config->scheduler_statusbar;
	
	long double pi_value = 0.0;
    long double work_done;
    long int n;
    
		
	/* variables required for the operation*/
	/*unsigned long int two;
	unsigned long int n2;
	unsigned long int n2p;
	two = 2;
	
	mpf_t dividend_float, divisor_float, pi;
	mpf_init(dividend_float);
	mpf_init(divisor_float);
	mpf_init(pi);
	
	mpz_t dividend;
	mpz_init (dividend);
	
	mpz_t base;
	mpz_init (base);
	mpz_set_ui(base, two);
	
	mpz_t divisor_A;
	mpz_init (divisor_A);
	
	mpz_t divisor_B;
	mpz_init (divisor_B);

	mpz_t divisor_C;
	mpz_init (divisor_C);
	
	mpz_t divisor_T;
	mpz_init (divisor_T);
	
	mpz_t divisor_F;
	mpz_init (divisor_F);
	
	mpf_t partial_result;
	mpf_init (partial_result);
	
	mpf_t final_result;
	mpf_init (final_result);
	mpf_set_ui(final_result, 0);*/
    
    /* Number of terms to complete! */    
    for(n = 0; n<units_to_complete; n++){
        

		/* operations */
		/*n2 = 2*n;
		n2p = 2*n+1;
		
		factorial((2*n), dividend); 
		mpz_pow_ui(divisor_A, base, n2);
		factorial(n, divisor_B);
		mpz_pow_ui(divisor_B, divisor_B, two);
		mpz_set_ui(divisor_C, n2p);
		mpz_mul(divisor_T, divisor_A, divisor_B);
		mpz_mul(divisor_F, divisor_T, divisor_C);		
		mpf_set_z(dividend_float, dividend);
		mpf_set_z(divisor_float, divisor_F);
		mpf_div(partial_result, dividend_float, divisor_float);
		mpf_add(final_result, final_result, partial_result);
		mpf_mul_ui (pi, final_result, two);
		pi_value = (long double)mpf_get_d(pi);*/
		
		pi_value = pi_value + 1.0;
		
		currentThread->current_result = pi_value;
        
        printf("Working.. %ld\n", n);
        
		///> Start of GUI update: 
		
		// Updating currently running thread and its value for PI
        
		str = g_strdup_printf("Thread %ld running, Pi value: %Lf", currentThread->id, currentThread->current_result);
		printf("%s \n", str);
        gtk_statusbar_push(GTK_STATUSBAR(sbar), gtk_statusbar_get_context_id(GTK_STATUSBAR(sbar), str), str);       

        work_done = currentThread->work_units - currentThread->work_left;
        work_done = work_done +  n +  1.0;
        work_done = work_done / currentThread->work_units;
        

        // Updating currently running thread's progress bar
        gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(pbar), (gdouble)work_done);
        str = g_strdup_printf("%.0f%%", (gdouble)work_done*100.0);
        gtk_progress_bar_set_text(GTK_PROGRESS_BAR(pbar), str);
        
        
        // DO NOT MOVE and call ONLY once inside this loop! :P
        while (g_main_context_iteration(NULL, FALSE));

        ///> End of GUI update
    }
    
    if ((currentThread->work_left - units_to_complete) == 0) {
		
        ///> Start of GUI update: 
        // Displaying Pi's final value in progress bar
        str = g_strdup_printf("%.0f%%, Pi: %Lf", (gdouble)work_done*100, currentThread->current_result);
        printf("%s \n", str);
        gtk_progress_bar_set_text(GTK_PROGRESS_BAR(pbar), str);
        
        while (g_main_context_iteration(NULL, FALSE));
        ///> End of GUI update
		
		/*clean space used by the objects*/
		/*mpz_clear(dividend);
		mpz_clear(base);
		mpz_clear(divisor_A);
		mpz_clear(divisor_B);
		mpz_clear(divisor_C);
		mpz_clear(divisor_T);
		mpz_clear(divisor_F);
		mpf_clear(partial_result);
		mpf_clear(final_result);
		mpf_clear(dividend_float);
		mpf_clear(divisor_float);
		mpf_clear(pi);*/
	}

    printNode(currentThread);
    continue_handler();
}


/*
 * initializeThreads: create and initialize threads pool
 * 
 * */
 
threadQueue* initializeThreads(configuration *config_in)
{   
    
    config = config_in;
    
    long int i,j,id =0, lotteryNum = 0;
    address_t stackPointer, programCounter;
    threadNode *threadInstance = NULL;
    long int threads_num = config->threads_num;
    long int *tickets_num = config->tickets_num;
    threadQueue* readyQueue = newQueue(threads_num);
    
    printf("\n%s\n", "Creating threads pool...");
    
    /* Initialize threads pool */
    for(i=0; i<threads_num ; i++) {
        
        /* Generate lottery tickets, given by a sequence */
        long int* tickets = (long int*) malloc(tickets_num[i]*sizeof(long int));
        
        for(j=0; j<tickets_num[i]; j++)
        {
            tickets[j] = lotteryNum;
            lotteryNum++;
        }
        
        /* Create a thread */
        
        threadInstance = newNode(id, config->work_units[i], tickets_num[i], tickets, config->quantum);
        stackPointer = (address_t)threadInstance->stack + STACK_SIZE - sizeof(address_t);
        
        /* TODO: Different operations for each thread?*/
        programCounter = (address_t)get_pi;

        sigsetjmp(threadInstance->env,1);
        
        (threadInstance->env->__jmpbuf)[JB_StackPointer] = translate_address(stackPointer);
        (threadInstance->env->__jmpbuf)[JB_ProgramCounter] = translate_address(programCounter);
        sigemptyset(&threadInstance->env->__saved_mask);
        
        addElement(readyQueue, threadInstance);
        
        id++;
        printNode(threadInstance);
        
    }
    
    return readyQueue;
        
    
}


void start_thread(threadNode *threadInstance)
{
    handler_active = 0;
	currentThread = threadInstance;
    siglongjmp(threadInstance->env,1);
}

int pause_thread(threadNode *threadInstance)
{
    int ret_val = sigsetjmp(threadInstance->env,1);
    
    if (ret_val == 1) {
        return 1;
    }
    return ret_val;
}


void continue_handler()
{
    handler_active = 1;
	siglongjmp(handler,1);
}

int pause_handler()
{
    handler_active = 0;
	int ret_val = sigsetjmp(handler,1);
    
    if (ret_val == 1) {
        return 1;
    }

    return ret_val;
}



/* Methods for the queue! */

threadNode *newNode(long int id, long int work_units, long int tickets_num, long int* tickets, long double quantum)
{
    threadNode *node = malloc(sizeof(*node));
    
    /* Check if malloc failed */
    if(node == NULL)
        fprintf(stderr, "Error: malloc() failed on file %s, function %s\n",__FILE__, "newNode");
    
    node->id = id;
    node->work_units = work_units;
    node->tickets_num = tickets_num;
    node->tickets = tickets;
    node->work_left = work_units;
    node->current_result = 0;
    node->quantum = quantum;
    
    return node;
    
}

threadQueue* newQueue(long int threads_num )
{
    threadQueue* list = malloc(threads_num*sizeof(threadQueue*));
    
    /* Check if malloc failed */
    if(list == NULL)
        fprintf(stderr, "Error: malloc() failed on file %s, function %s\n",__FILE__, "newQueue");
    
     /* Initialize the node's data */
    list->head = list->tail = NULL;
    
    return list;
    
}

threadQueue* addElement (threadQueue* list, threadNode* node)
{
    /* Check if malloc failed */
    if(node == NULL)
    {
        fprintf(stderr, "Error: malloc() failed on file %s, function %s\n",__FILE__, "addElement");
        return list;
    }
    
    /* Check that the list was initialized list == NULL*/
    
    /* Empty list */
    if(list->head == NULL && list->tail == NULL)
        list -> head = list ->tail = node;
    
    /* List with elements*/
    else 
    {
        list->tail->next = node;
        list->tail = node;
    }
    
    return list;
}

threadQueue* removeElement (threadQueue* list, threadNode* nodeToDelete)
{
    
    threadNode* node = NULL;
    threadNode* previousNode = NULL;
    
    if(list->head == NULL && list->tail == NULL)
        return list;
    else {
        
         for(node = list->head; node; node = node->next)
         {
            if(nodeToDelete->id == node->id)
            {
                 
                /* When the node is the head */
                if(previousNode == NULL) {
                    list->head = node->next;
                    
                    if(node->next == NULL)
                        list->tail = NULL;
                
                }
                else    
                /* When the node is the tail */
                if(node->next == NULL) {
                    previousNode->next = NULL;
                    list->tail = previousNode;
                }
                else
                    previousNode->next = node->next;
                
                /* Clean data */
                nodeToDelete->next = NULL;
                
                return list;
            }
            
            previousNode = node;    
        }
                
        
    }
    
    return list;
    
    
}


void moveElement (threadQueue* listSource, threadQueue* destination, threadNode* node)
{
    if(node != NULL){
        removeElement(listSource,node);
        addElement(destination,node);
    }
    
}


void printQueue(threadQueue* list)
{
    threadNode* node = NULL;
    
    if(list)
    {
        for(node = list->head; node; node = node->next)
            printNode(node);
        
    }
    
    printf("\n-END-\n");
    
}

void printNode(threadNode* node)
{
       
    if(node)
    
    {
        printf("\n-----| Thread %ld |-----\n",node->id);
        printf("\t- Work Units: %ld \n",node->work_units);
        printf("\t- Tickets: %ld \n",node->tickets_num);
        
        printf("\t %s", " - Tickets List:");
        for(long int i = 0; i < node->tickets_num; i++) {
            printf("%ld ",node->tickets[i]);
        }
        
        printf("\n\t- Work Left: %ld \n",node->work_left);
        printf("\t- Quantum: %Lf \n",node->quantum);
        printf("\t- Current Result: %Lf \n\n",node->current_result);

    }
    
}

void freeQueue(threadQueue *list)
{
    threadNode *node = list->head;
    threadNode *current = NULL;
    
    while(node)
    {
        current = node;
        node = node->next;
        
        freeNode(current);
    }
}
    
    
void freeNode(threadNode *node)
{
    free(node->tickets);
    free(node);
}
